#!/usr/bin/env bash

# 项目根目录
CODECLI_DIR=$HOME/VscodeCLI

# web 下载地址（用于计算）
CODECLI_DLURL="https://vscode.download.prss.microsoft.com/dbazure/download/stable/%s/vscode-server-linux-%s-web.tar.gz"

# 脚本文件名
ARGS0="$(basename ${BASH_SOURCE[0]})"

# 开放模式
WEB_MODEL=${WEB_MODEL:-default}
case $WEB_MODEL in
  default|0) # 默认
    CODE_WEB_HOST=${CODECLI_HOST:-127.0.0.1};;
  localhost|1) # 本机
    CODE_WEB_HOST=127.0.0.1;;
  intranet|2) # 内网
    WEB_IP_ADDR=$(ip a | grep -w 'inet' | grep -v 'host' | tr '/' ' ' | awk '{print $2}')
    if [ -z $WEB_IP_ADDR ];then
      echo "[!] 找不到地址" >&2
      exit 1
    else
      CODE_WEB_HOST=$WEB_IP_ADDR
    fi;;
  wholev4|3) # IPv4
    CODE_WEB_HOST=0.0.0.0;;
  wholev6|4) # IPv6
    CODE_WEB_HOST='[::]';;
  *) # 意料之外
    echo "[!] $WEB_MODEL 意料之外的模式" >&2
    exit 1
esac
CODE_WEB_PORT=${CODECLI_PORT:-8000}

# 所有的目录
CODECLI_DIRS=(
  $CODECLI_DIR/home
  $CODECLI_DIR/codecli
  $CODECLI_DIR/user
  $CODECLI_DIR/extensions
  $CODECLI_DIR/log
  $CODECLI_DIR/server
  $CODECLI_DIR/bin/code
)

# 监听主机和端口
CODECLI_HAP=(
  $CODE_WEB_HOST
  $CODE_WEB_PORT
)

# 启动参数
CODECLI_ARGS=(
  HOME=${CODECLI_DIRS[0]}
  ${CODECLI_DIRS[6]} serve-web
  --without-connection-token
  --cli-data-dir=${CODECLI_DIRS[1]}
  --user-data-dir=${CODECLI_DIRS[2]}
  --extensions-dir=${CODECLI_DIRS[3]}
  --server-data-dir=${CODECLI_DIRS[5]}
  --host=${CODECLI_HAP[0]}
  --port=${CODECLI_HAP[1]}
)

# 文件检索
CODECLI_GS=${CODECLI_DIRS[5]}/data/User/globalStorage/storage.json
CODECLI_LOG=${CODECLI_DIRS[4]}/start.log
CODECLI_PROC=${CODECLI_DIRS[4]}/proc.log
CODECLI_DATE=${CODECLI_DIRS[4]}/date.log
CODECLI_HAPF=${CODECLI_DIRS[4]}/hap.log

# 一些循环和判断
if [ ! -f ${CODECLI_DIRS[6]} ];then
  echo "[!] 找不到可执行文件 ${CODECLI_DIRS[6]}" >&2
  exit 1
fi

for i in ${CODECLI_DIRS[@]};do
  [[ -d $i || -f $i ]] || mkdir -p $i
done

# 函数 监测状态/地址
CODECLI_GET_ADDR(){
  grep "Web UI available at" $CODECLI_LOG | grep -o http.*
  return $?
}
CODECLI_STATE(){
  pgrep -f "${CODECLI_ARGS[1]} " &>/dev/null
  return $?
}

# 命令参数处理
case $1 in
start)
  if ! CODECLI_STATE;then
    echo "[#] 尝试启动服务"
    eval "${CODECLI_ARGS[@]} 1>$CODECLI_LOG &"
    CODECLI_PID=$!
    CODECLI_GETADDR="$(
       SLEEP=100
       until CODECLI_GET_ADDR;do
         [[ $SLEEP == 0 ]] && break
         sleep 0.01
         let SLEEP--
       done
    )"
    if [ -z $CODECLI_GETADDR ];then
      echo "[!] 服务启动失败 [$CODECLI_PID]" >&2
      rm -f $CODECLI_LOG
    else
      echo "${CODECLI_HAP[0]} ${CODECLI_HAP[1]}" > $CODECLI_HAPF
      CODECLI_WEB_URL="http://${CODECLI_HAP[0]}:${CODECLI_HAP[1]}"
      echo "[*] 地址: $CODECLI_WEB_URL"
      echo "[*] 进程号: $CODECLI_PID"
      if [[ ! -f $CODECLI_GS ]];then
        echo "[#] 这可能是初次启动，等待资源下载..."
        printf "\e[?25l"
        COMMIT=$(${CODECLI_DIRS[6]} -V | awk '{print $4}' | tr -d '()')
        case $(uname -m) in
          armv7* | armv8l) ARCH=armhf;;
          aarch64 | armv8* | arm64 | arm*) ARCH=arm64;;
          x86_64 | amd64) ARCH=x64
        esac
        url=$(printf "$CODECLI_DLURL" $COMMIT $ARCH)
        SIZE=$(curl -sLI $url | awk '/Content-Length/ {print $2}' 2>/dev/null)
        START_TIME=$(date +%s)
        POST_TEXT="Unsupported method POST"
        CODECLI_ODLS=0
        rm -rf /tmp/.tmp*/ /tmp/code-*
        while true;do
          CURL_POST="$(curl -sX POST "$CODECLI_WEB_URL")"
          if [[ "$CURL_POST" == *"$POST_TEXT"* ]];then
            for i in {1..5};do
              printf "\e[K\n"
            done
            printf "\e[5A"
            printf "\e[?25h"
            echo -e "\r[#] 资源下载完成。"
            echo "[?] 服务正在运行，您可以继续访问以上地址"
            break
          else
            END_TIME="$(date +%s)"
            SECONDS=$((END_TIME - START_TIME))
            ((SECONDS))||SECONDS=1
            CODECLI_TMP_DIR=$(ls -1A /tmp | grep -E '^\.tmp[0-9a-zA-Z]{6}$')
            CODECLI_DLS=$(du -sb /tmp/$CODECLI_TMP_DIR/vscode* 2>/dev/null | awk '{print $1}')
            CODECLI_DLLS=$(( (CODECLI_DLS - CODECLI_ODLS) / SECONDS))
            CODECLI_DS="$(echo $CODECLI_DLLS | awk '{printf "%.2f", $1/1024/1024}')MB/s"
            CODECLI_SDL="$(echo $CODECLI_DLS | awk '{printf "%.2f", $1/1024/1024}')MB"
            CODECLI_SBL="$(echo $SIZE $CODECLI_DLS | awk '{printf "%.2f", $2/$1*100}')"
            if [[ $CODECLI_SBL == 100.* ]];then
              unset CODECLI_SBL
              CODECLI_DL1=100.00
            elif [[ $CODECLI_SBL == 0.00 ]];then
              unset CODECLI_DL1
            fi
            ((CODECLI_DLS))||CODECLI_DLS=1
            AS=$((CODECLI_DLS / SECONDS))
            RS="$(echo $SIZE $CODECLI_DLS | awk '{printf "%.0f", $1-$2}')"
            if ((AS));then
              ETA=$((RS / AS))
              ETA_H=$((ETA / 3600))
              ETA_M=$(((ETA % 3600) / 60))
              ETA_S=$((ETA % 60))
            else
              ETA_H=0 ETA_M=0 ETA_S=0
            fi
            ETA_T=$(printf "%02d:%02d:%02d" $ETA_H $ETA_M $ETA_S)
            US_H=$((SECONDS / 3600))
            US_M=$(((SECONDS % 3600) / 60))
            US_S=$((SECONDS % 60))
            US_T=$(printf "%02d:%02d:%02d" $US_H $US_M $US_S)
            printf "\e[?25l"
            echo -e "已下载   $CODECLI_SDL\e[K"
            echo -e "速率     $CODECLI_DS\e[K"
            echo -e "下载进度 $CODECLI_SBL$CODECLI_DL1%\e[K"
            echo -e "剩余时间 $ETA_T\e[K"
            echo -e "已用时间 $US_T\e[K"
            printf "\e[5A"
          fi
        done
      fi
      echo $CODECLI_PID > $CODECLI_PROC
      date +%s.%N > $CODECLI_DATE
    fi
  else
   CODECLI_HAP_HOST=$(awk '{print $1}' $CODECLI_HAPF)
   CODECLI_HAP_PORT=$(awk '{print $2}' $CODECLI_HAPF)
   CODECLI_HAP_IF=0
   [[ ${CODECLI_HAP[0]} == $CODECLI_HAP_HOST ]] ||
   let CODECLI_HAP_IF++
   [[ ${CODECLI_HAP[1]} == $CODECLI_HAP_PORT ]] ||
   let CODECLI_HAP_IF++
   if (( CODECLI_HAP_IF > 0 ));then
      echo -e "[!] 已有正在运行的服务\n[?] 但 主机/端口 不一致 ( != ${CODECLI_HAP[0]}:${CODECLI_HAP[1]} )\n[?] 详见 $ARGS0 state" >&2
    else
      echo "[!] 已有正在运行的服务" >&2
    fi
  fi
  ;;
stop)
  if CODECLI_STATE;then
    echo "[#] 尝试终止服务"
    pkill -f ${CODECLI_ARGS[1]} &>/dev/null
    if pgrep -f "${CODECLI_ARGS[1]}" &>/dev/null;then
      echo "[!] 进程终止失败/误判，存在相关残留进程" >&2
    else
      echo "[#] 服务已终止"
      rm -f $CODECLI_PROC $CODECLI_DATE $CODECLI_HAPF $CODECLI_LOG
    fi
  else
    echo "[!] 暂无正在运行的服务" >&2
  fi
  ;;
state)
    if CODECLI_STATE;then
      CODECLI_HAP_HOST=$(awk '{print $1}' $CODECLI_HAPF)
      CODECLI_HAP_PORT=$(awk '{print $2}' $CODECLI_HAPF)
      CODECLI_PID=$(cat $CODECLI_PROC 2>/dev/null || echo Null)
      CODECLI_TIME=$(printf "%.3f %.3f" $(date +%s.%N) $(cat $CODECLI_DATE) | awk '{printf ("%.3f", $1-$2)}')
      TIME_SECONDS=$(cut -d '.' -f1 <<< $CODECLI_TIME)
      TIME_NANOSE=$(cut -d '.' -f2 <<< $CODECLI_TIME)
      TIME_HOURS=$((TIME_SECONDS / 3600))
      TIME_MIN=$((TIME_SECONDS % 3600 / 60))
      TIME_SECONDS=$((TIME_SECONDS % 60))
      CODECLI_TIME=$(printf  "%02d:%02d:%02d.%03d" $TIME_HOURS $TIME_MIN $TIME_SECONDS $TIME_NANOSE)
      CODECLI_RSS="$(cat /proc/$CODECLI_PID/status | grep VmRSS | awk '{print $2, $3}')"
      CE=$(grep 'New connection established' $CODECLI_LOG | tail -1 | grep -oE '([0-9]{2}:){2}[0-9]{2}' | awk '!seen[$0]++')
      [ -z $CE ] && CE="暂无连接记录"
      echo "[#] 服务状态"
      echo "[*] 正在运行"
      echo "[*] 地址: $CODECLI_HAP_HOST"
      echo "[*] 端口: $CODECLI_HAP_PORT"
      echo "[*] 进程号: $CODECLI_PID"
      echo "[*] 运行时长: $CODECLI_TIME"
      echo "[*] 内存占用: $CODECLI_RSS"
      echo "[*] 最近一次访问记录: $CE"
    else
      echo "[#] 服务状态"
      echo "[*] 未运行"
    fi
  ;;
remove)
    if CODECLI_STATE;then
      echo "[!] 请停止服务后再进行卸载" >&2
    else
      rm -rf \
      ${CODECLI_DIRS[0]} \
      ${CODECLI_DIRS[1]} \
      ${CODECLI_DIRS[2]} \
      ${CODECLI_DIRS[3]} \
      ${CODECLI_DIRS[4]} \
      ${CODECLI_DIRS[5]}
  fi
  ;;
version)
  if ${CODECLI_ARGS[1]} -V 2>/dev/null;then
    echo "code-cli web-serve proc manager script [$(date -d@$(stat -c %Y ${BASH_SOURCE[0]}) +%Y%m%d%H%M)]"
    serve_dir=$(cat ${CODECLI_DIRS[1]}/serve-web/lru.json | tr -d '[]"')
    nodev=$(${CODECLI_DIRS[1]}/serve-web/$serve_dir/node -v)
    osn=$(source /etc/os-release;echo $NAME)
    bashv=$(echo ${BASH_VERSINFO[@]: 0: 3} | tr ' ' '.')
    echo "Created By $osn & Node.js $nodev & bash $bashv"
  else
    echo "[!] 版本获取失败，请检查 vsc cli 是否被正确安装" >&2
  fi
  ;;
run)
  [ -z $2 ] && exit
  ${CODECLI_ARGS[1]} ${@: 2}
  ;;
update)
  if CODECLI_STATE;then
    echo "[!] 请停止服务后再进行更新" >&2
  else
    eval "${CODECLI_ARGS[0]} ${CODECLI_ARGS[1]} ${CODECLI_ARGS[4]} ${CODECLI_ARGS[5]} ${CODECLI_ARGS[6]} update"
    STAT=$?
    if ((STAT));then
      echo "[!] 更新失败或已是最新" >&2
    else
      echo -e "[*] 更新完成"
    fi
  fi
  ;;
help|*)
  [[ ! -z $1 && ! $1 == help ]] && echo "[!] $ARGS0: $1: 未知的选项" >&2
  cat << EOF
vscode-cli web 服务进程管理脚本
子命令:
  start    启动服务
  stop     终止服务
  state    显示服务状态
  remove   卸载服务
  run      执行 cli 命令
  update   更新 code-cli
  version  显示版本
  help     显示此帮助

在使用子命令 run 时，脚本不会为 code-cli 提供内建环境
所有参数和环境变量均来自外部。

此外，可通过变量传递启动时使用的主机/端口
CODECLI_HOST 和 CODECLI_PORT

当然，使用子命令 run 时它们不会被 code-cli 接受

例如:
CODECLI_HOST=192.168.1.10 CODECLI_PORT=8080 $ARGS0 start

还可以使用预设的开放模式，同样通过变量传递
WEB_MODEL=default/localhost/intranet

关于开放模式
default: 默认，在没有传递 CODECLI_HOST
         时将默认使用本机地址 (127.0.0.1)
localhost: 使用本机地址 (127.0.0.1)
intranet: 自动获取除本机外的地址 (IPv4)，在无法获取时直接退出
wholev4: 开放全部 IPv4 地址
wholev6: 开放全部 IPv6 地址

CODECLI_PORT 在未指定时默认为 8000

注意，这仅在使用子命令 start 时有效
其他子命令使用的主机/端口在服务启动后就已定下
且只能启动一个服务，不允许多个服务共存

EOF
esac